Esercitazione 3
Reti sincronizzate
Le reti sincronizzate sono reti logiche con uno stato interno, mantenuto usando registri, che si evolvono a instanti discreti dati da un segnale di clock. In questa esercitazione vedremo come realizzarle, simularle e studiarle nell'ambiente d'esame.
Testbench e generatore di clock
Per poter simulare una rete sincronizzata dobbiamo innanzitutto avere un generatore di clock. Il segnale di clock è segnale oscillante, che dal punto di vista logico appare come in figura.
In realtà i generatori di clock sono basati su cristalli di quarzo, un materiale piezoelettrico con il quale si possono realizzare circuiti oscillanti. Questi circuiti emettono una tensione oscillante come mostrato in figura (da Wikimedia), notare come l'onda sia molto meno squadrata di quanto presentato al livello logico.
Per i nostri usi, ci basterà descrivere una rete asincrona che cambia il proprio segnale da 0 a 1, e viceversa, ad intervalli regolari.
Una qualunque descrizione realistica, e dunque sintetizzabile, dovrebbe avere a che fare con un segnale di reset che indichi a questa rete da che punto cominciare.
Dato che però vogliamo usare questo generatore in una testbench simulativa, possiamo utilizzare direttamente i concetti relativi, approfittando della keyword initial
per mantenere il codice semplice.
// generatore del segnale di clock
module clock_generator(
clock
);
output clock;
parameter HALF_PERIOD = 5;
reg CLOCK;
assign clock = CLOCK;
initial CLOCK <= 0;
always #HALF_PERIOD CLOCK <= ~CLOCK;
endmodule
Notiamo che questa rete non è sintetizzabile. Infatti, utilizza la keyword initial
, che è priva di senso in hardware, e un reg
non come registro ma come variabile, come già visto nelle testbench.
Questo si nota dal fatto che il reg
non viene aggiornato in risposta ad un altro segnale, come il positive edge del clock, come invece accade per registri.
Il parametro HALF_PERIOD
rende il periodo di questo generatore di clock configurabile.
Tipicamente all'esame viene utilizzato il valore default di 5, che implica periodi di clock di 10 unità di tempo.
Qualora questo cambiasse (per esempio, per permettere reti combinatorie con maggior tempo di attraversamento) sarà segnalato nel testo.
Come ogni altra rete, questa viene inclusa nella testbench con una instanziazione.
module testbench();
wire clock;
clock_generator clk(
.clock(clock)
);
...
mia_rete dut(
...
.clock(clock)
);
...
Oltre al segnale di clock, una rete sincronizzata avrà bisogno anche del segnale di reset.
Questo viene aggiunto come un reg
pilotato all'inizio del blocco initial
della testbench.
module testbench();
...
reg reset_;
...
mia_rete dut(
...
.clock(clock), .reset_(reset_)
);
...
initial begin
reset_ = 0;
#(clk.HALF_PERIOD);
reset_ = 1;
...
end
Con la sintassi #(clk.HALF_PERIOD);
si attendono unità di tempo proporzionali al periodo di clock configurato. Questo è utile ad evitare di modificare manualmente tutte le attese in caso di cambio di clock.
Primo esempio di rete sincronizzata: il contatore
Vediamo ora un semplice esempio di rete sincronizzata, un contatore. Questa rete ha un registro di 3 bit inizializzato a 0, che viene incrementato ad ogni ciclo di clock, facendo infine wrap-around da 7 a 0. Il codice è scaricabile qui.
module contatore (
out,
clock, reset_
);
output [2:0] out;
input clock, reset_;
reg [2:0] OUT;
assign out = OUT;
always @(reset_ == 0) begin
OUT <= 0;
end
always @(posedge clock) if (reset_ == 1) #3 begin
OUT <= OUT + 1;
end
endmodule
Vediamo quindi come questo codice modella il comportamento di una vera rete sincronizzata. Il reg
OUT
viene collegato direttamente all'uscita out
, viene inizializzato a 0 solo in corrispondenza del segnale di reset (righe 11-13) e, durante la normale operazione, viene aggiornato con il valore OUT + 1
in corrispondenza di un posedge
del clock.
Il ritardo #3
modella il tempo del registro.
<=
È importante, nelle reti sincronizzate, utilizzare <=
per assegnamenti a registri sul fronte positivo del clock.
Questo perché gli statement con <=
sono intesi come eseguiti in parallelo, non sequenzialmente.
Infatti, i registri non sono variabili e i loro cambiamenti non sono visibili agli altri registri fino al successivo posedge del clock.
Possiamo vedere come si evolve questa rete, simulandola in una testbench con segnale di clock e reset_ (scaricabile qui). Per il resto, la testbench non fa altro che attendere diversi cicli di clock, visto che questa rete non ha alcun input e si evolve in autonomia.
Vediamo l'evoluzione della rete usando GTKWave.
Osserviamo, in particolare, il registro OUT
e il segnale del clock.
Notiamo che a ogni fronte positivo del clock, OUT
non cambia immediatamente, ma solo dopo 3 unità di tempo.
Dalla teoria sui registri, ricordiamo anche che il nuovo valore assunto dal registro deve essergli dato in input da prima del posedge e fino a